﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using Commands.Commands;
using Commands.Scheduling;
using Commands.Settings;
using Microsoft.Practices.ObjectBuilder2;
using Microsoft.Practices.Unity;

namespace Commands
{
    public interface IMethodsManager
    {
        bool TryInvokeCommand(Command command);

        IEnumerable<ParameterAttribute> GetCommandParameters(string commandName);
    }

    /// <summary>
    /// Class for managing commands.
    /// </summary>
    internal class MethodsManager : IMethodsManager
    {
        public MethodsManager(IEnumerable<CommandListBase> commandsList, ICommandManager manager)
        {
            _container = _CreateUnityContainer(manager);

            // Fill dictionary with methods.
            foreach (var list in commandsList)
            {
                if (CommandsListAnalyzer.IsListWellDescribed(list, _container))
                {
                    Dictionary<MethodInfo, List<string>> methods = CommandsListAnalyzer.GetMethodsDictionary(list);

                    _commandsDictionary.Add(list, methods);
                }
                    // Otherwise do not include such list to commands dictionary.
                else
                    Debug.Assert(false);
            }
        }

        /// <summary>
        /// Execute command.
        /// </summary>
        /// <param name="command"></param>
        /// <param name="manager"></param>
        /// <returns></returns>
        public bool TryInvokeCommand(Command command)
        {
            var commandName = command.Name.ToLower();

            foreach (var commandList in _commandsDictionary.Keys)
            {
                var method = _commandsDictionary[commandList].FirstOrDefault(
                    x => x.Value.Contains(commandName)).Key;
                if (method != null)
                {
                    var parameters = _helper.CreateMethodParameters(method.GetParameters(), command, _container);

                    try
                    {
                        command.Succeeded = (bool)method.Invoke(commandList, parameters.ToArray());
                    }
                    catch (Exception ex)
                    {
                        command.Exception = ex;
                    }

                    return true;
                }
            }

            return false;
        }

        public IEnumerable<ParameterAttribute> GetCommandParameters(string commandName)
        {
            var allCommands = GetCommandsList();
            var method = allCommands.FirstOrDefault(x => x.Value.Contains(commandName)).Key;

            var result = new List<ParameterAttribute>();

            if (method != null)
                result.AddRange(ParameterAttribute.GetMetodParameters(method));

            return result;
        }

        public Dictionary<MethodInfo, List<string>> GetCommandsList()
        {
            return (_commandsDictionary).SelectMany(x => x.Value).ToDictionary(pair => pair.Key, pair => pair.Value);
        }

        private IUnityContainer _CreateUnityContainer(ICommandManager manager)
        {
            IUnityContainer container = new UnityContainer();

            container.RegisterInstance(manager.Log);
            container.RegisterInstance<ISchedule>(manager.Schedule);
            container.RegisterInstance<MethodsManager>(this);

            return container;
        }

        private static ParameterAttribute _FindParameterAttribute(
            IList<ParameterAttribute> paramsAttribute, ParameterInfo parameterInfo)
        {
            // Try to get parameter info by type.
            var parametersOfRightType = paramsAttribute.Where(x => x.Type == parameterInfo.ParameterType);

            ParameterAttribute result;
            // If we found one parameter of this type - we found what we need.
            if (parametersOfRightType.Count() == 1)
                result = parametersOfRightType.First();
            // Else - get parameter by name.
            else
            {
                result = parametersOfRightType.FirstOrDefault(x => x.Names.Contains(parameterInfo.Name));
            }

            if (result == null)
                // Remove found attribute so we wouldn't find it again for other method parameter.
                paramsAttribute.Remove(result);

            return result;
        }


        private Dictionary<CommandListBase, Dictionary<MethodInfo, List<string>>> _commandsDictionary =
            new Dictionary<CommandListBase,Dictionary<MethodInfo,List<string>>>();
        private IUnityContainer _container;
        private ParametersHelper _helper = new ParametersHelper(
            FilesHelper.DefaultDownloadDir, SettingsHelper.Settings.DefaultPassword);

    }
}
